#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_err.h"
#define LOG_LOCAL_LEVEL ESP_LOG_NONE
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_touch.h"
#include "lcd_touch_ns2009.h"

static const char* TAG = "NS2009";

/* NS2009 registers */
#define NS2009_LOW_POWER_READ_X  0xc0
#define NS2009_LOW_POWER_READ_Y  0xd0
#define NS2009_LOW_POWER_READ_Z1 0xe0
#define NS2009_LOW_POWER_READ_Z2 0xf0

#define SCREEN_X_PIXEL 320
#define SCREEN_Y_PIXEL 480

/*******************************************************************************
 * Function definitions
 *******************************************************************************/
static esp_err_t esp_lcd_touch_ns2009_read_data(esp_lcd_touch_handle_t tp);
static bool      esp_lcd_touch_ns2009_get_xy(esp_lcd_touch_handle_t tp, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_num, uint8_t max_point_num);
static esp_err_t esp_lcd_touch_ns2009_del(esp_lcd_touch_handle_t tp);

/* I2C read/write */
static esp_err_t touch_ns2009_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t* data, uint8_t len);
static esp_err_t touch_ns2009_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data);

uint16_t ns2009_read(esp_lcd_touch_handle_t tp, uint8_t _cmd);

/* NS2009 reset */
static esp_err_t touch_ns2009_reset(esp_lcd_touch_handle_t tp);
/* Read status and config register */
static esp_err_t touch_ns2009_read_cfg(esp_lcd_touch_handle_t tp);

/*******************************************************************************
 * Public API functions
 *******************************************************************************/

esp_err_t esp_lcd_touch_new_i2c_ns2009(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t* config, esp_lcd_touch_handle_t* out_touch)
{
    esp_err_t ret = ESP_OK;

    assert(io != NULL);
    assert(config != NULL);
    assert(out_touch != NULL);

    esp_log_level_set(TAG, ESP_LOG_DEBUG);

    ESP_LOGD(TAG, "esp_lcd_touch_new_i2c_ns2009 init");

    /* Prepare main structure */
    esp_lcd_touch_handle_t esp_lcd_touch_ns2009 = heap_caps_calloc(1, sizeof(esp_lcd_touch_t), MALLOC_CAP_DEFAULT);
    ESP_GOTO_ON_FALSE(esp_lcd_touch_ns2009, ESP_ERR_NO_MEM, err, TAG, "no mem for NS2009 controller");

    /* Communication interface */
    esp_lcd_touch_ns2009->io = io;

    /* Only supported callbacks are set */
    esp_lcd_touch_ns2009->read_data = esp_lcd_touch_ns2009_read_data;
    esp_lcd_touch_ns2009->get_xy    = esp_lcd_touch_ns2009_get_xy;
    esp_lcd_touch_ns2009->del       = esp_lcd_touch_ns2009_del;

    /* Mutex */
    esp_lcd_touch_ns2009->data.lock.owner = portMUX_FREE_VAL;

    /* Save config */
    memcpy(&esp_lcd_touch_ns2009->config, config, sizeof(esp_lcd_touch_config_t));

    /* Prepare pin for touch interrupt */
    if(esp_lcd_touch_ns2009->config.int_gpio_num != GPIO_NUM_NC)
    {
        const gpio_config_t int_gpio_config = {
            .mode         = GPIO_MODE_INPUT,
            .intr_type    = GPIO_INTR_NEGEDGE,
            .pin_bit_mask = BIT64(esp_lcd_touch_ns2009->config.int_gpio_num)};
        ret = gpio_config(&int_gpio_config);
        ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed");

        /* Register interrupt callback */
        if(esp_lcd_touch_ns2009->config.interrupt_callback)
        {
            esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_ns2009, esp_lcd_touch_ns2009->config.interrupt_callback);
        }
    }

    /* Prepare pin for touch controller reset */
    if(esp_lcd_touch_ns2009->config.rst_gpio_num != GPIO_NUM_NC)
    {
        const gpio_config_t rst_gpio_config = {
            .mode         = GPIO_MODE_OUTPUT,
            .pin_bit_mask = BIT64(esp_lcd_touch_ns2009->config.rst_gpio_num)};
        ret = gpio_config(&rst_gpio_config);
        ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed");
    }

    /* Reset controller */
    ret = touch_ns2009_reset(esp_lcd_touch_ns2009);
    ESP_GOTO_ON_ERROR(ret, err, TAG, "NS2009 reset failed");

err:
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Error (0x%x)! Touch controller NS2009 initialization failed!", ret);
        if(esp_lcd_touch_ns2009)
        {
            esp_lcd_touch_ns2009_del(esp_lcd_touch_ns2009);
        }
    }

    *out_touch = esp_lcd_touch_ns2009;

    ESP_LOGD(TAG, "esp_lcd_touch_new_i2c_ns2009 success");

    return ret;
}


static uint16_t last_adc_x;
static uint16_t last_adc_y;

static esp_err_t esp_lcd_touch_ns2009_read_data(esp_lcd_touch_handle_t tp)
{
    esp_err_t err;
    uint8_t   buf[41];
    uint8_t   touch_cnt = 0;
    uint8_t   clear     = 0;
    size_t    i         = 0;

    assert(tp != NULL);

    ESP_LOGD(TAG, "esp_lcd_touch_ns2009_read_data\n");
    // err = touch_ns2009_i2c_read(tp, ESP_LCD_TOUCH_NS2009_READ_XY_REG, buf, 1);
    // ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");

    uint16_t z = ns2009_read(tp, NS2009_LOW_POWER_READ_Z1);

    if((z > 50) && (z < 2000))
    {
        uint16_t x_adc = ns2009_read(tp, NS2009_LOW_POWER_READ_X);
        uint16_t y_adc = ns2009_read(tp, NS2009_LOW_POWER_READ_Y);

        ESP_LOGD(TAG, "esp_lcd_touch_ns2009_get_xy,adc:%d,%d,%d\n", x_adc, y_adc, z);

        last_adc_x = x_adc;
        last_adc_y = y_adc;

        tp->data.points             = 1;
        tp->data.coords[0].x        = x_adc * SCREEN_X_PIXEL / 4096;    // 4096 = 2 ^ 12
        tp->data.coords[0].y        = SCREEN_Y_PIXEL - (y_adc * SCREEN_Y_PIXEL / 4096);
        tp->data.coords[0].strength = z;
    }
    else
    {
        tp->data.points             = 0;
        tp->data.coords[0].strength = z;
    }

    return ESP_OK;
}



// 在这里加入 tslib
static bool esp_lcd_touch_ns2009_get_xy(esp_lcd_touch_handle_t tp, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_num, uint8_t max_point_num)
{
    assert(tp != NULL);
    assert(x != NULL);
    assert(y != NULL);
    assert(point_num != NULL);
    assert(max_point_num > 0);

    portENTER_CRITICAL(&tp->data.lock);

    /* Count of points */
    *point_num = tp->data.points;

    *x        = tp->data.coords[0].x;
    *y        = tp->data.coords[0].y;
    // *strength = tp->data.coords[0].strength;

    portEXIT_CRITICAL(&tp->data.lock);

    return (*point_num > 0);
}

static esp_err_t esp_lcd_touch_ns2009_del(esp_lcd_touch_handle_t tp)
{
    assert(tp != NULL);

    /* Reset GPIO pin settings */
    if(tp->config.int_gpio_num != GPIO_NUM_NC)
    {
        gpio_reset_pin(tp->config.int_gpio_num);
    }

    /* Reset GPIO pin settings */
    if(tp->config.rst_gpio_num != GPIO_NUM_NC)
    {
        gpio_reset_pin(tp->config.rst_gpio_num);
    }

    free(tp);

    return ESP_OK;
}

/*******************************************************************************
 * Private API function
 *******************************************************************************/

/* Reset controller */
static esp_err_t touch_ns2009_reset(esp_lcd_touch_handle_t tp)
{
    assert(tp != NULL);

    if(tp->config.rst_gpio_num != GPIO_NUM_NC)
    {
        ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level error!");
        vTaskDelay(pdMS_TO_TICKS(10));
        ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level error!");
        vTaskDelay(pdMS_TO_TICKS(10));
    }

    return ESP_OK;
}


uint16_t ns2009_read(esp_lcd_touch_handle_t tp, uint8_t _cmd)
{
    esp_err_t ret       = ESP_OK;
    uint8_t   tp_adc[2] = {0};

    ret = touch_ns2009_i2c_read(tp, _cmd, tp_adc, 2);
    ESP_GOTO_ON_ERROR(ret, err, TAG, "touch_ns2009_i2c_read error");

err:
    return (tp_adc[0] << 8 | tp_adc[1]) >> 4;
}


static esp_err_t touch_ns2009_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t* data, uint8_t len)
{
    esp_err_t ret = ESP_OK;

    assert(tp != NULL);
    assert(data != NULL);

    /* Read data */
    // esp_lcd_panel_io_tx_param(tp->io, reg, NULL, 0);
    ret = esp_lcd_panel_io_rx_param(tp->io, reg, data, len);
    ESP_LOGD(TAG, "touch_ns2009_i2c_read:%X", ret);
    return ret;
}

static esp_err_t touch_ns2009_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data)
{
    assert(tp != NULL);

    // *INDENT-OFF*
    /* Write data */
    return esp_lcd_panel_io_tx_param(tp->io, reg, (uint8_t[]){data}, 1);
    // *INDENT-ON*
}
